
// ------------------------
// projects/c++/date/Date.h
// Copyright (C) 2009
// Glenn P. Downing
// ------------------------

#ifndef Date_h
#define Date_h

// --------
// includes
// --------

#include <cassert>   // assert
#include <iostream>  // ostream
#include <stdexcept> // invalid_argument


// ----------
// namespaces
// ----------

namespace Date {

// ----
// Date
// ----

	static int months[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
	const static char* monthnames[12] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};

template <typename T>
class Date {
    private:
        // ----
        // data
        // ----


        // <your data>
	int day;
	int month;
	int year;

    private:
        // -----
        // valid
        // -----

        /**
	* Checks if the date is valid
	* @return bool. True if valid date and false otherwise 
         */
        bool valid () const {
			// <your code>
			if(day < 1 || day >  31 || month < 1 || month > 12 || year < 1600) return false;
			else if (leap_year() && month == 2) return day <= 29;
			else return day <= months[month-1];
			/*
			if (month == 2)
				if(this->leap_year()) return day <= 29; else return day <= 28;
			else if((month + (month/8)) & 1 == 1) //checks if the month is one with 31 days
				return day <= 31;
			else
				return day <= 30;
			 */
        }

        // -----------
        // constructor
        // -----------

        /**
         * O(1)   in space
         * O(1)   in time
         * @param days >= 0
         * Date(0) -> 1 Jan 1600
         * you can loop through an array of month days
         */
        Date (const T& days) {
			if (days < 0)
				throw std::invalid_argument("Date::Date()");
			// <your code>

			int d = days;
			year = 1600;
			year += 400* (d/146097);
			d %= 146097; //now working with a block of 400 years max.
			if(d <= 364) //we have a century leap year
				months[1] = 29;
			else {
				year += 100* ((d-1)/36524);
				//Dec 31 fix...
				d = (d%36524 == 0) ? 36524 : (d%36524); //now working with a block of 100 years max
				if(d < 365) //we have a century false-leap year
					months[1] = 28;
				else {
					year += 4* (d/1461);
					d %= 1461; //now working with a block of 4 years max
						if( d >= 366){ //it is not the leap year
							year++;
							d -= 366;
							year += d/365;
							d = d%365;
							months[1] = 28;
						} else months[1] = 29; //and d remains within 0-366
				}
			}
			month = 0;
			while( d >= months[month]) {
				d -= months[month];
				month++;
			} month++;
			day = d + 1;
		}

        // -------
        // to_days
        // -------
    public:
        /**
         * O(1)    in space
         * O(1)    in time
         * @return the number of days since 1 Jan 1600, >= 0
         * 1 Jan 1600 -> 0
         */
        T to_days () const {
            assert(valid());
			T days = 0;
			// <your code>
			days += (year - 1600) * 365;

			days += (year - 1596) / 4;
			days -= (year - 1600) / 100;
			days += (year - 1600) / 400;

			if(leap_year()) {
				months[1] = 29;
				days--; //take out the leap day until we're sure we actually have it
			}
			else months[1] = 28;

			for(int i = 0; i < month - 1; i++) //month - 1 because the current month hasn't been completed
				days += months[i];

			days += day - 1; // -1 because we must count days *SINCE* 1 Jan 1600

			assert(days >= 0);
			return days;
		}


		std::ostream& print(std::ostream& out) const {
			return out << day << " " << monthnames[ month - 1] << " " << year;
        }

    public:
        // -----------
        // constructor
        // -----------

        /**
         * O(1)    in space
         * O(1)    in time
         * @param  day   >= 1 && <= 31
         * @param  month >= 1 && <= 12
         * @param  year  >= 1600
         * @throws std::invalid_argument if the resulting date is invalid
         */
        Date (const T& d, const T& m, const T& y) {
            // <your code>
			day = d;
			month = m;
			year = y;
			if (!valid())
				throw std::invalid_argument("Date::Date()");
        }

        // Default copy, destructor, and copy assignment.
        // Date (const Date&);
        // ~Date ();
        // Date& operator = (const Date&);

        // -----------
        // operator +=
        // -----------

        /**
		 * Adds days to a date and returns modified date 
         * @param  days the number of days to add (may be negative!)
         * @return the date resulting from adding days
         * @throws std::invalid_argument if the resulting date precedes 1 Jan 1600
         */
        Date& operator += (const T& days) {
            // <your code>
        	if(to_days() + days < 0)
        		throw std::invalid_argument("Date::operator+=(const T&)");
        	Date temp(to_days() + days);
        	day = temp.day; month = temp.month; year = temp.year;
            return *this;
        }

        // -----------
        // operator -=
        // -----------

        /**
		 * Subtracts days from a date and returns modified date 
         * @param  days the number of days to subtract (may be negative!)
         * @return the date resulting from subtracting days
         * @throws std::invalid_argument if the resulting date precedes 1 Jan 1600
         */
        Date& operator -= (const T& days) {
            // <your code>
        	if(to_days() - days < 0)
        		throw std::invalid_argument("Date::operator-=(const T&)");
        	Date temp(to_days() - days);
        	day = temp.day; month = temp.month; year = temp.year;
            return *this;
        }

        // ---------
        // leap_year
        // ---------

        /**
		 * Checks whether a given year is a leap year 
	* @return bool. True if leap year and false otherwise.
         */
        bool leap_year () const {
            // <your code>
        	if (year % 4 == 0 && year % 100 != 0 || year % 400 == 0)
				return true;
        	else 	return false;
        }
	};

// -----------
// operator ==
// -----------

/**
 * Compares two dates. Checks if they are equal.
 * @param Date<T>& lhs, Date<T>& rhs.
 * @return bool. True if they are equal and false otherwise.

 
 */
template <typename T>
bool operator == (const Date<T>& lhs, const Date<T>& rhs) {
	// <your code>
	return lhs.to_days() == rhs.to_days();
	}

// ----------
// operator <
// ----------

/**
  * Compares two dates. Checks if one of them is less than the other. 
 * @param Date<T>& lhs, Date<T>& rhs.
 * @return bool. True if lhs is less than rhs and false otherwise.
 */
template <typename T>
bool operator < (const Date<T>& lhs, const Date<T>& rhs) {
	// <your code>
	return lhs.to_days() < rhs.to_days();
	}

// ----------
// operator +
// ----------

/**
 * Adds days to a date.
 * @param Date<T> lhs, const T& rhs
  *@return Returns the resulting date by value, leaving original date unmodified. 
 * @throws std::invalid_argument if the resulting date precedes 1 Jan 1600
 */
template <typename T>
Date<T> operator + (Date<T> lhs, const T& rhs) {
	if(lhs.to_days() + rhs < 0)
		throw std::invalid_argument("Date::operator+(Date<T>, T)");
	return lhs += rhs;
	}

// ----------
// operator -
// ----------

/**
 * Subtracts days from a date.
 * @param Date<T> lhs, const T& rhs
  *@return Returns the resulting date by value, leaving original date unmodified. 

 * @throws std::invalid_argument if the resulting date precedes 1 Jan 1600
 */
template <typename T>
Date<T> operator - (Date<T> lhs, const T& rhs) {
	if(lhs.to_days() - rhs < 0)
		throw std::invalid_argument("Date::operator-(Date<T>, T)");
	return lhs -= rhs;}

/**
 * Subtracts two dates. The result indicates how many days they are apart.
 * @param Date<T>& lhs, Date<T>& rhs
  *@return Returns a T of resulting difference
 * @return the number of days between the dates (lhs - rhs)
 */
template <typename T>
T operator - (const Date<T>& lhs, const Date<T>& rhs) {
	// <your code>
	return lhs.to_days() - rhs.to_days();
	}

// -----------
// operator <<
// -----------

/**
 * O(1)    in space
 * O(1)    in time
 * @param  lhs an ostream
 * @param  rhs a date
 * @return the ostream
 * output a string representation of the date (e.g. "3 Feb 2008")
 * you can loop through an array of month names
 */
template <typename T>
std::ostream& operator << (std::ostream& lhs, const Date<T>& rhs) {
	// <your code>
	return rhs.print(lhs);
	}

} // Date

#endif // Date_h

